package com.jerry.boot.business.auth;

import cn.hutool.captcha.CaptchaUtil;
import cn.hutool.captcha.ShearCaptcha;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.RSA;
import cn.hutool.extra.servlet.ServletUtil;
import com.jerry.boot.business.auth.pojo.AuthDto;
import com.jerry.boot.business.auth.pojo.LoginReq;
import com.jerry.boot.business.auth.pojo.ReLoginDto;
import com.jerry.boot.business.user.User;
import com.jerry.boot.business.user.UserService;
import com.jerry.boot.core.JConstant;
import com.jerry.boot.core.base.ApiException;
import com.jerry.boot.core.base.JConfigureProperties;
import com.jerry.boot.core.web.ServletContextHelper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Date;

@Service
@Slf4j
public class AuthServiceImpl implements AuthService {

    private final JConfigureProperties jConfigureProperties;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    public AuthServiceImpl(JConfigureProperties jConfigureProperties) {
        this.jConfigureProperties = jConfigureProperties;
    }

    @Resource
    private UserService userService;

    @Override
    public AuthDto login(LoginReq loginReq) {
        /**
         * 验证验证码
         */
        String sessionYzm = (String) ServletContextHelper.getSession().getAttribute("yzm");
        if (StrUtil.isBlank(sessionYzm)) {
            throw new ApiException(102);
        }
        if (!sessionYzm.equalsIgnoreCase(loginReq.getYzm())) {
            throw new ApiException(102);
        }

        String username = loginReq.getUsername();
        String password = loginReq.getPassword();
        boolean rememberMe = loginReq.isRememberMe();
        /**
         * 先用RSA解密前端传过来的账号密码
         */
        RSA rsa = new RSA(jConfigureProperties.getRsaPrivateKey(), null);
        username = rsa.decryptStr(username, KeyType.PrivateKey);
        password = rsa.decryptStr(password, KeyType.PrivateKey);
        /**
         * 用账号查询DB user
         */
        User user = userService.getUserByUsername(username);
        if (user == null) {
            throw new ApiException(401);
        }
        /**
         * 判断用户状态是否启用
         */
        if (user.getStatus() != 1) {
            throw new ApiException(401);
        }
        /**
         * 用用户的md5盐+password加密和数据库用户的密文密码做比对
         */
        String securePass = SecureUtil.md5(user.getSalt() + password);
        if (!user.getPassword().equals(securePass)) {
            throw new ApiException(401);
        }
        /**
         * 用户登录验证通过存入session,spring session会自动持久化session
         */
        HttpSession session = ServletContextHelper.getSession();
        AuthDto authDto = new AuthDto();
        authDto.setUsername(user.getUsername());
        /**
         * 如果记住登录，生成accessToken & refreshToken 存入redis，并保存accessToken和refreshToken的映射对到redis
         */
        if (rememberMe) {
            String accessToken = IdUtil.fastSimpleUUID();
            authDto.setAccessToken(accessToken);
            String refreshToken = IdUtil.fastSimpleUUID();
            authDto.setRefreshToken(refreshToken);
            stringRedisTemplate.opsForValue().set("access:token:" + accessToken, username, jConfigureProperties.getTokenSaveTime());
            stringRedisTemplate.opsForValue().set("refresh:token:" + refreshToken, username, jConfigureProperties.getRefreshTokenSaveTime());
            stringRedisTemplate.opsForValue().set(accessToken, refreshToken, jConfigureProperties.getRefreshTokenSaveTime());
            authDto.setRefreshTokenTimeOut(DateUtil.offsetMillisecond(new Date(), (int) jConfigureProperties.getRefreshTokenSaveTime().toMillis()));
        }
        session.setAttribute(JConstant.AUTH_USER_KEY, authDto);
        userService.updateUserLastLoginTime(username);
        return authDto;
    }

    @Override
    public void logout() {
        ServletContextHelper.getSession().invalidate();
        String accessToken = ServletUtil.getHeader(ServletContextHelper.getRequest(), JConstant.ACCESS_TOKEN, "UTF-8");
        if (StrUtil.isNotBlank(accessToken)) {
            stringRedisTemplate.delete("access:token:" + accessToken);
            stringRedisTemplate.delete(accessToken);
            String refreshToken = stringRedisTemplate.opsForValue().get(accessToken);
            if (StrUtil.isNotBlank(refreshToken)) {
                stringRedisTemplate.delete("refresh:token:" + refreshToken);
            }
        }
    }

    @Override
    public String getYzm() {
        ShearCaptcha captcha = CaptchaUtil.createShearCaptcha(120, 40, 4, 4);
        // 重新生成code
        captcha.createCode();
        String code = captcha.getCode();
        ServletContextHelper.getSession().setAttribute("yzm", code);
        HttpServletResponse response = ServletContextHelper.getResponse();
        try (OutputStream os = response.getOutputStream()) {
            captcha.write(os);
            os.flush();
        } catch (IOException e) {
            log.error("com.itsu.bootl.business.auth.AuthServiceImpl.getYzm", e);
            throw new ApiException(101);
        }
        return code;
    }

    @Override
    public AuthDto reLogin(ReLoginDto reLoginDto) {
        //根据之前的accessToken查询redis中的refreshToken
        String redisRefreshToken = stringRedisTemplate.opsForValue().get(reLoginDto.getAccessToken());
        //判断保存的refreshToken和上送的refreshToken是否一致
        if (!reLoginDto.getRefreshToken().equals(redisRefreshToken)) {
            throw new ApiException(404);
        }
        //通过refreshToken查询所属user
        String username = stringRedisTemplate.opsForValue().get("refresh:token:" + reLoginDto.getRefreshToken());
        if (StrUtil.isBlank(username)) {
            throw new ApiException(404);
        }
        //查询数据库中的用户信息
        User user = userService.getUserByUsername(username);
        if (user == null) {
            throw new ApiException(404);
        }
        //删除redis中的accessToken，正常来说此accessToken已经失效，保险起见进行一次删除
        stringRedisTemplate.delete("access:token:" + reLoginDto.getAccessToken());
        //删除redis中保存的原accessToken和refreshToken映射对
        stringRedisTemplate.delete(reLoginDto.getAccessToken());
        AuthDto authDto = new AuthDto();
        authDto.setUsername(user.getUsername());
        String accessToken = IdUtil.fastSimpleUUID();
        authDto.setAccessToken(accessToken);
        authDto.setRefreshToken(redisRefreshToken);
        //生成新的accessToken并保存至redis
        stringRedisTemplate.opsForValue().set("access:token:" + accessToken, user.getUsername(), jConfigureProperties.getTokenSaveTime());
        //建立新的accessToken和refreshToken映射对
        stringRedisTemplate.opsForValue().set(accessToken, redisRefreshToken);
        authDto.setRefreshTokenTimeOut(DateUtil.offsetMillisecond(new Date(), (int) jConfigureProperties.getRefreshTokenSaveTime().toMillis()));
        ServletContextHelper.getSession().setAttribute(JConstant.AUTH_USER_KEY, authDto);
        return authDto;
    }
}
